<template>
  <view class="v-keyboard" @click="toggleKeyboard" v-show="showKeyboard">
    <!-- 遮罩 -->
    <view class="mask" v-if="mask"></view>
    <!-- 具体的键盘布局 -->
    <view class="wrapper" @click.stop>
      <!-- 工具栏 -->
      <div class="wrapper-tool" v-if="tool">
        <text>{{ text }}</text>
      </div>
      <!-- 画按钮框框 -->

      <view class="wrapper-buttons">
        <view
          :class="{ 'wrapper-item': true, disabled: number === '' }"
          :disabled="number === ''"
          v-for="number in numbers"
          :key="number"
          @click="onSelect(number)"
          >{{ number }}</view
        >
        <view class="wrapper-item backspace" @touchstart="handleTouchStart" @touchmove="handleTouchMove" @touchend="handleTouchEnd" @click="onBackspace"></view>
      </view>
      <!-- 安全距离 -->
      <view class="env-safe-bottom"></view>
    </view>
  </view>
</template>

<script>
/**
 * keyboard自定义键盘
 * description 自定义键盘
 * @property {String, Number} value 当前输入的内容
 * @property {Boolean} mask = [true|false] 是否显示遮罩
 * @property {String} type = ['digital'|'idcard'|'tel'] 键盘类型
 * @property {Boolean} auto = [true|false] 如果为 true 需要设置 maxLength 的长度，当输入长度和 maxLength 一致，会触发 confirm 回调
 * @property {Boolean} tool = [true|false] 显示/隐藏工具栏
 * @property {Boolean} show = [true|false] 显示/隐藏键盘
 * @property {String} text = '数字键盘' 自定义工具栏文字提示
 * @property {Number, String} max-length 能输入的最大长度，为 0 的话不限制长度
 * @property {String} confirm-text = '确认' 确认按钮的文字
 * @event {Function} confirm 点击完成触发
 * @example <v-keyboard v-model="inputValue" @confirm="onCondirm"></v-keyboard>
 */
export default {
  name: 'VKeyboard',
  emits: ['confirm', 'update:show', 'update:modelValue'],
  props: {
    // #ifdef VUE3
    modelValue: [String, Number],
    // #endif
    // #ifdef VUE2
    value: [String, Number],
    // #endif
    auto: Boolean,
    mask: Boolean,
    type: {
      require: false,
      type: String,
      default: 'digital',
      validator: (val) => ['digital', 'idcard', 'tel'].includes(val)
    },
    tool: Boolean,
    show: Boolean,
    text: {
      type: String,
      default: '数字键盘'
    },
    maxLength: {
      type: [Number, String],
      default: 0
    },
    confirmText: {
      type: String,
      default: '确定'
    }
  },
  data() {
    return {
      showKeyboard: false,
      inputValue: '',
      typeMap: {
        digital: [1, 2, 3, 4, 5, 6, 7, 8, 9, '.', 0],
        idcard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 'X', 0],
        tel: [1, 2, 3, 4, 5, 6, 7, 8, 9, '', 0]
      }
    }
  },
  computed: {
    numbers() {
      return this.typeMap[this.type] || this.typeMap['digital']
    },
    inputValueArr: ({ inputValue }) => `${inputValue}`.split('').map(String),
    hasDot: ({ inputValueArr }) => inputValueArr.indexOf('.') > -1
  },
  watch: {
    // #ifdef VUE2
    value: {
      immediate: true,
      handler(newVal) {
        this.inputValue = newVal
      }
    },
    // #endif
    show: {
      immediate: true,
      handler(newVal) {
        this.showKeyboard = newVal
      }
    },
    inputValue(newVal) {
      // #ifdef VUE2
      this.$emit('update:value', newVal)
      // #endif
      // #ifdef VUE3
      this.$emit('update:modelValue', newVal)
      // #endif
      // 自动触发 @confirm 回调
      if (this.maxLength !== 0 && this.inputValueArr.length === this.maxLength * 1 && this.auto) {
        this.$emit('confirm', newVal)
        this.toggleKeyboard()
      }
    }
  },
  mounted() {
    // #ifdef VUE3
    this.inputValue = this.modelValue
    // #endif
  },
  methods: {
    toggleKeyboard() {
      this.showKeyboard = !this.showKeyboard
      this.$emit('update:show', this.showKeyboard)
    },
    onBackspace() {
      if (this.inputValueArr.length === 0) {
        return
      }
      this.inputValue = this.inputValueArr.slice(0, this.inputValueArr.length - 1).join('')
    },
    onSelect(value) {
      if (this.hasNoPass(value)) return
      let currentValue
      // 如果是第一次点击.的话，变成0.
      if (this.inputValueArr.length === 0 && value === '.') {
        currentValue = '0.'
      } else {
        currentValue = [...this.inputValueArr, value].join('')
      }
      this.inputValue = currentValue
    },
    hasNoPass(value) {
      // 如果是数字，整个数字中 . 只能出现一次
      if (this.type === 'digital' && this.hasDot && value === '.') {
        return true
      }
      // 如果已经是 0 开头的话，第二位连着不能是0开头
      if (this.type === 'digital' && this.inputValueArr[0] === '0' && this.inputValueArr.length === 1 && value !== '.') {
        return true
      }
      // 如果 maxLength 不为 0 的话，只能输入 maxLength 指定长度的
      if (['idcard', 'tel'].includes(this.type) && this.maxLength !== 0) {
        return this.inputValueArr.length >= this.maxLength
      }

      return false
    },
    handleTouchStart(e) {
      const { pageX, pageY } = e.touches[0]
      this.pos = {
        pageX,
        pageY
      }
      this.timeoutTimer = setTimeout(() => {
        this.intervalTimer = setInterval(this.onBackspace, 100)
      }, 300)
    },
    handleTouchMove(e) {
      const { pageX, pageY } = e.touches[0]
      if (Math.abs(this.pos.pageX - pageX) <= 10 && Math.abs(this.pos.pageY - pageY) < 10) {
        this.handleTouchEnd()
      }
    },
    handleTouchEnd() {
      clearTimeout(this.timeoutTimer)
      clearInterval(this.intervalTimer)
    }
  }
}
</script>

<style lang="scss" scoped>
$bg: #e7e7e7;
$bg-active: #dbdbdb;
$color: #1a2a1a;
$fontSize: 40upx;
$btn-border-radius: 10upx;
$btn-margin: 16upx;
$btn-height: 100upx;

.v-keyboard {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 1001;

  .mask {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background: rgba(0, 0, 0, 0.3);
    z-index: 1002;
  }

  .wrapper {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    background-color: $bg;
    font-size: $fontSize;
    color: $color;
    user-select: none;
    z-index: 1003;
    padding-top: 10upx;
    overflow: hidden;

    &-tool {
      text-align: center;
      color: #666;
      font-size: 32upx;
      padding: 16rpx 0;
    }

    &-buttons {
      display: flex;
      flex-wrap: wrap;
    }

    &-item {
      display: flex;
      align-items: center;
      justify-content: center;
      width: calc(33% - #{$btn-margin});
      height: $btn-height;
      border-radius: $btn-border-radius;
      background-color: #fff;
      margin-bottom: $btn-margin;
      margin-left: $btn-margin;
      font-weight: bold;

      &.backspace {
        background-image: url('');
        background-repeat: no-repeat;
        background-size: 60upx;
        background-position: center center;
        color: $color;
      }

      &:nth-child(10),
      &:nth-child(12) {
        background-color: transparent;
      }

      &:nth-child(10),
      &:nth-child(11),
      &:nth-child(12) {
        margin-bottom: 10upx;
      }

      &:not(.disabled):active {
        background-color: $bg-active;
      }

      &:disabled {
        background-color: transparent !important;
      }
    }
  }

  /** 底部安全距离 */
  .env-safe-bottom {
    height: constant(safe-area-inset-bottom);
    height: env(safe-area-inset-bottom);
  }
}
</style>
